以目前而言,OAuth 2.0 授權框架(下簡稱 OAuth2)定義了一個相較安全的授權流程。但身分驗證並不在 OAuth2 的範疇內,而今天簡介的 OpenID Connect 正是基於 OAuth2 的基礎上,再另外定義了身分驗證的流程。
OpenID Connect 的協定於 2014 年發布,裡面有用到了 OAuth2 與 JWT,相較 SAML 來說,算是一個非常年輕的協定。
這裡有趣的地方是:OAuth2 為 2012 年發布,JWT 則為 2015 年發布,這是因為 OpenID Connect 是參考 JWT 的草稿文件。
筆者是使用 ORY Hydra (下簡稱 Hydra)這個開源服務,Hydra 也是 OpenID 官方認證符合協定的實作之一。ORY Hydra 提到他們稱自己的實作為 User Login and Consent Flow,裡面完整了說明身分驗證與授權流程,非常好理解。以下會以 Hydra 的文件為主,來介紹 OpenID Connect。
因基於 OAuth2 上實作新的協定,所以有部分內容會跟 OAuth2 重覆
時序圖如下:
@startuml
UserAgent -> Client: Access protected resource
UserAgent <- Client: (1) Redirect to Authorization Server
UserAgent -> AuthorizationServer: (1) Initial Authorization Code Flow or Implict Flow
UserAgent <- AuthorizationServer: (2) Redirect to Login Provider
UserAgent -> LoginProvider: (2) Challange and Authenticate user
LoginProvider <--> AuthorizationServer: (3) Fetch login info && accept login
UserAgent <- LoginProvider: (3) Redirect to Authorization Server
UserAgent -> AuthorizationServer: (3) Check login ifno
UserAgent <- AuthorizationServer: (4) Redirect to Consent Provider
UserAgent -> ConsentProvider: (5) Request grant
ConsentProvider <--> AuthorizationServer: (6) Fetch scope info
UserAgent <- ConsentProvider: (6) Redirect to Authorization Server
UserAgent -> AuthorizationServer: (6) Check consent info
UserAgent <- AuthorizationServer: (7) Redirect to Client
UserAgent <- Client: (7) With Code
Client --> AuthorizationServer: (8) Send token endpoint
Client <-- AuthorizationServer: (8) Get access token and id token
@enduml
流程描述如下
http://hydra/oauth2/auth?client_id=...&...
。http://login-provider/login?login_challenge=1234...
。http://hydra/oauth2/auth?client_id=...&...&login_verifier=4321
。http://consent-service/consent?consent_challenge=4567...
。http://hydra/oauth2/auth?client_id=...&...&consent_verifier=7654...
。這裡因為有多個服務傳來傳去的很複雜,下面額外說明細節。
與 OAuth2 類似,但 OpenID Connect 額外的定義如下:
GET
或 POST
openid
response_mode
、nonce
等下面是以 Line 為例的 GET 請求:
https://access.line.me/oauth2/v2.1/authorize?response_type=code&scope=openid%20profile&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fline%2Fcallback&state=40c798406415ea0409416d76c2596430&client_id=1234567890
若 OpenID Provider 還不認識使用者的話(指沒有 session 或 cookie),則會要求使用者做身分驗證。在 Hydra 的流程定義裡,Login Provider 的任務即專心做身分驗證即可,符合單一職責原則,這裡回頭看一下過去提到的身分驗證可以怎麼做有提到哪些方法:
其中 API 身分驗證跟此情境比較不符,所以不參考。但帳號密碼驗證或第三方身分驗證都是可以在 Login Provider 實作的,這代表 OpenID Connect 登入的過程,可以再繼續使用其他的 OpenID Provider 做身分驗證。舉個例子,如:使用 Hydra 做為 OpenID Provider,同時 Login Provider 提供帳號密碼驗證,也提供 Line 登入。
Hydra 的做法是將 Login Provider 分離,而我們來看看 Line,當 Client 發出授權請求時,Line 會將使用者導去驗證的 URL 如下:
https://access.line.me/oauth2/v2.1/login?returnUri=%2Foauth2%2Fv2.1%2Fauthorize%2Fconsent%3Fscope%3Dopenid%2Bprofile%26response_type%3Dcode%26state%3Db1e4221e89abe958df760cb8bcada23e%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A8080%252Fline%252Fcallback%26client_id%3D1653300970&loginChannelId=1234567890&loginState=Rb31VRbWP2ubLbMbxiQGC
這裡是 Line 登入的畫面。
從 URL 可以大略看得出來,https://access.line.me/oauth2/v2.1/authorize
是在處理 OAuth2 授權請求,而 https://access.line.me/oauth2/v2.1/login
則是處理身分驗證,這概念與 Hydra 所提到的 Login Provider 是雷同的。
在驗證完身分後,接著就會繼續走 [OAuth2 的授權流程][Day 24]。其中有一步是要求使用者授權,RFC 6749 裡面,而 Hydra 則是提到要額外一個叫 Consent Provider 的服務來處理。這個服務可以把 Client 所請求的授權列在介面上讓使用者確認。
以 Line 來說,Consent Provider 是實作下面這個 URL 的:
https://access.line.me/oauth2/v2.1/authorize/consent?scope=openid+profile&response_type=code&state=b1e4221e89abe958df760cb8bcada23e&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fline%2Fcallback&client_id=1234567890
這裡是 Line 授權的畫面。
而 openid
也是個 Scope,Line 對於它的說明為:
用戶識別資訊(必要資訊)
由LINE指派的唯一內部識別資訊
Consent Provider 與 Login Provider 類似,服務是實作在另一個路徑上的,下面把 Line 三個端口再列表一次:
endpoint | URL |
---|---|
Authorization Endpoint | https://access.line.me/oauth2/v2.1/authorize |
Login Provider | https://access.line.me/oauth2/v2.1/login |
Consent Provider | https://access.line.me/oauth2/v2.1/authorize/consent |
從 URL 約略可以了解,這可能是做在同個服務上的。
而 Hydra 的設計是把 Login Provider 與 Consent Provider 抽象化,也就是它定義了這兩個 provider 要做什麼事,以及如何與 Hydra 交換資訊。這樣設計的好處是:
在最後授權完成後,使用者會將 code
轉傳給 Client,Client 即可拿 code
去跟授權伺服器換 token。換 token 的方法與 OAuth2 一樣,需要帶 client_id
與 client_secret
,但回傳內容會多了 ID Token,格式即為 JWT。以 Line 為例,裡面解出來的 claim 內容如下:
{
"iss": "https://access.line.me",
"sub": "a1b2c3",
"aud": "1234567890",
"exp": 1570783265,
"iat": 1570779665,
"amr": [
"linesso"
],
"name": "Miles Chou",
"picture": "https://profile.line-scdn.net/xxxxx"
}
裡面 iss
很明確說明了這個 JWT 是由 Line 發行的;aud
則說明了這個 JWT 是要給 1234567890
這個 Client 使用的,這會跟一開始發送請求時帶的 client_id
參數相同等等。
有的資訊之前有提過就不再重覆。
到目前為止,配合 Hydra 的 User Login and Consent Flow 以及實際串接 Line 的過程,即能大概了解 OpenID Connect 的運作原理。
Line 是使用 Authorization Code Flow,所以剛剛的說明主要都是在描述 Authorization Code Flow 的流程。